/*****************************************************************************
Serial (mouse) driver code
Chris Giese <geezer@execpc.com>	http://www.execpc.com/~geezer
Release date: Oct 13, 2003
This code is public domain (no copyright).
You can do whatever you want with it.

Updated Dec 27, 2003:
- can now use F1 and F2 keys to toggle RTS and DTR

NOTE: This code probably won't work inside a Windows DOS box.

To do
- Watcom C delay() seems slower than Turbo C or DJGPP
- detect presence of modem?
- maybe write and test a LapLink clone
- test in system with 16650+ (32-byte) FIFO
*****************************************************************************/

/* define this for serial mouse demo,
undefine it for e.g. modem */
#define	MOUSE

/*----------------------------------------------------------------------------
Portability code for DOS compilers
----------------------------------------------------------------------------*/
/* Turbo C:	getvect(), setvect() [in|out]portb(), [peek|poke][b]()
DJGPP:		[in|out]portb()
Watcom C:	MK_FP(), dos_getvect(), _dos_setvect() */
#include <dos.h>

#define	VECTOR_MAGIC	0xB179

/* forward declarations */
int irq_enabled_at_8259(unsigned irq);
void enable_irq_at_8259(unsigned irq);
void disable_irq_at_8259(unsigned irq);

extern const unsigned g_irq_to_vect[];
/********************************* TURBO C **********************************/
#if defined(__TURBOC__)

#define	peekw(S,O)	peek(S,O)
#define	pokew(S,O,V)	poke(S,O,V)
#ifdef __cplusplus
#define	HANDLER_ARGS	...
#else
#define	HANDLER_ARGS	void
#endif
#define	HANDLER(X)	void interrupt X(HANDLER_ARGS)

typedef struct
{
	unsigned irq_num, vect_num, magic;
	char enabled;
	void interrupt (*ov)(HANDLER_ARGS);
} vector_t;
/*****************************************************************************
*****************************************************************************/
int hook_irq(vector_t *vector, unsigned irq_num,
		void interrupt (*handler)(HANDLER_ARGS))
{
	if(irq_num >= 16)
		return -1;
	vector->irq_num = irq_num;
	vector->vect_num = g_irq_to_vect[irq_num];
	vector->enabled = irq_enabled_at_8259(vector->irq_num);

	vector->ov = getvect(vector->vect_num);
	setvect(vector->vect_num, handler);

	enable_irq_at_8259(vector->irq_num);
	vector->magic = VECTOR_MAGIC;
	return 0;
}
/*****************************************************************************
*****************************************************************************/
void unhook(vector_t *vector)
{
	if(vector->magic != VECTOR_MAGIC)
		return;
	if(!vector->enabled)
		disable_irq_at_8259(vector->irq_num);

	setvect(vector->vect_num, vector->ov);
}
/********************************* DJGPP ************************************/
#elif defined(__DJGPP__)
#include <sys/farptr.h> /* _farpeekw() */
#include <go32.h> /* _dos_ds, _my_cs() */
#include <dpmi.h> /* _go32_dpmi..., __dpmi... */
#include <crt0.h> /* _crt0_startup_flags, _CRT0_FLAG_LOCK_MEMORY */

#define	peekb(S,O)	_farpeekb(_dos_ds, 16uL * (S) + (O))
#define	pokeb(S,O,V)	_farpokeb(_dos_ds, 16uL * (S) + (O), V)
#define	peekw(S,O)	_farpeekw(_dos_ds, 16uL * (S) + (O))
#define	pokew(S,O,V)	_farpokew(_dos_ds, 16uL * (S) + (O), V)
#define	HANDLER(X)	void X(void)

typedef struct
{
	unsigned irq_num, vect_num, magic;
	char enabled;
	_go32_dpmi_seginfo ov, nv;
} vector_t;

/* lock all memory, to prevent it being swapped or paged out */
int _crt0_startup_flags = _CRT0_FLAG_LOCK_MEMORY;
/*****************************************************************************
*****************************************************************************/
int hook_irq(vector_t *vector, unsigned irq_num, void (*handler)(void))
{
	if(irq_num >= 16)
		return -1;
	vector->irq_num = irq_num;
	vector->vect_num = g_irq_to_vect[irq_num];
	vector->enabled = irq_enabled_at_8259(vector->irq_num);

	_go32_dpmi_get_protected_mode_interrupt_vector(vector->vect_num,
		&vector->ov);
	vector->nv.pm_selector = _my_cs();
	vector->nv.pm_offset = (unsigned long)handler;
	_go32_dpmi_allocate_iret_wrapper(&vector->nv);
	_go32_dpmi_set_protected_mode_interrupt_vector(vector->vect_num,
		&vector->nv);

	enable_irq_at_8259(vector->irq_num);
	vector->magic = VECTOR_MAGIC;
	return 0;
}
/*****************************************************************************
*****************************************************************************/
void unhook(vector_t *vector)
{
	if(vector->magic != VECTOR_MAGIC)
		return;
	if(!vector->enabled)
		disable_irq_at_8259(vector->irq_num);

	_go32_dpmi_set_protected_mode_interrupt_vector(vector->vect_num,
		&vector->ov);
	_go32_dpmi_free_iret_wrapper(&vector->nv);
}

/******************************** WATCOM C **********************************/
#elif defined(__WATCOMC__)
#include <conio.h> /* inp(), outp() */

#if defined(__386__)
/* CauseWay DOS extender only */
#define	peekb(S,O)	*(unsigned char *)(16uL * (S) + (O))
#define	pokeb(S,O,V)	*(unsigned char *)(16uL * (S) + (O)) = (V)
#define	peekw(S,O)	*(unsigned short *)(16uL * (S) + (O))
#define	pokew(S,O,V)	*(unsigned short *)(16uL * (S) + (O)) = (V)
#else
#define	peekb(S,O)	*(unsigned char far *)MK_FP(S,O)
#define	pokeb(S,O,V)	*(unsigned char far *)MK_FP(S,O) = (V)
#define	peekw(S,O)	*(unsigned short far *)MK_FP(S,O)
#define	pokew(S,O,V)	*(unsigned short far *)MK_FP(S,O) = (V)
#endif
#define	inportb(P)	inp(P)
#define	outportb(P,V)	outp(P,V)
#define	HANDLER(X)	void __interrupt X(void)

typedef struct
{
	unsigned irq_num, vect_num, magic;
	char enabled;
	void __interrupt (*ov)(void);
} vector_t;
/*****************************************************************************
*****************************************************************************/
int hook_irq(vector_t *vector, unsigned irq_num,
		void __interrupt (*handler)(void))
{
	if(irq_num >= 16)
		return -1;
	vector->irq_num = irq_num;
	vector->vect_num = g_irq_to_vect[irq_num];
	vector->enabled = irq_enabled_at_8259(vector->irq_num);

	vector->ov = _dos_getvect(vector->vect_num);
	_dos_setvect(vector->vect_num, handler);

	enable_irq_at_8259(vector->irq_num);
	vector->magic = VECTOR_MAGIC;
	return 0;
}
/*****************************************************************************
*****************************************************************************/
void unhook(vector_t *vector)
{
	if(vector->magic != VECTOR_MAGIC)
		return;
	if(!vector->enabled)
		disable_irq_at_8259(vector->irq_num);

	_dos_setvect(vector->vect_num, vector->ov);
}
#else
#error Not Turbo C, not DJGPP, not Watcom C. Sorry.
#endif

/* IRQ-to-vector-number mapping
programmed into the 8259 chips by the BIOS: */
const unsigned g_irq_to_vect[] =
{
/* IRQ 0-7 */	8, 9, 10, 11, 12, 13, 14, 15,
/* IRQ 8-15 */	112, 113, 114, 115, 116, 117, 118, 119
};
/*****************************************************************************
*****************************************************************************/
int irq_enabled_at_8259(unsigned irq)
{
	if(irq >= 16)
		return 0;
	if(irq >= 8)
	{
		if(inportb(0x21) & ~0x04)
			return 0;
		irq -= 8;
		irq = 1 << irq;
		return (inportb(0xA1) & irq) == 0;
	}
	irq = 1 << irq;
	return (inportb(0x21) & irq) == 0;
}
/*****************************************************************************
*****************************************************************************/
void enable_irq_at_8259(unsigned irq)
{
	if(irq >= 16)
		return;
	if(irq >= 8)
	{
		outportb(0x21, inportb(0x21) & ~0x04);
		irq -= 8;
		irq = 1 << irq;
		outportb(0xA1, inportb(0xA1) & ~irq);
		return;
	}
	irq = 1 << irq;
	outportb(0x21, inportb(0x21) & ~irq);
}
/*****************************************************************************
*****************************************************************************/
void disable_irq_at_8259(unsigned irq)
{
	if(irq >= 16)
		return;
	if(irq >= 8)
	{
		/* outportb(0x21, inportb(0x21) | 0x04); */
		irq -= 8;
		irq = 1 << irq;
		outportb(0xA1, inportb(0xA1) | irq);
		return;
	}
	irq = 1 << irq;
	outportb(0x21, inportb(0x21) | irq);
}
/*----------------------------------------------------------------------------
Circular queues
----------------------------------------------------------------------------*/
typedef struct
{
	unsigned char *buf;
	unsigned size, in_ptr, out_ptr;
} queue_t;
/*****************************************************************************
*****************************************************************************/
static int inq(queue_t *q, unsigned char data)
{
	unsigned temp;

	temp = q->in_ptr + 1;
	if(temp >= q->size)
		temp = 0;
/* if in_ptr reaches out_ptr, the queue is full */
	if(temp == q->out_ptr)
		return -1;
	q->buf[q->in_ptr] = data;
	q->in_ptr = temp;
	return 0;
}
/*****************************************************************************
this function used to look like this:
	static int deq(queue_t *q, unsigned char *data)
but Watcom C produces broken code for such a function. I don't know why.
*****************************************************************************/
static int deq(queue_t *q)
{
	int rv;

/* if out_ptr reaches in_ptr, the queue is empty */
	if(q->out_ptr == q->in_ptr)
		return -1;
	rv = q->buf[q->out_ptr];
	q->out_ptr++;
	if(q->out_ptr >= q->size)
		q->out_ptr = 0;
	return rv;
}
/*----------------------------------------------------------------------------
Serial I/O using FIFO and interrupt-driven transmit & receive
----------------------------------------------------------------------------*/
/*****************************************************************************
*****************************************************************************/
#include <string.h> /* memset() */
#include <conio.h> /* kbhit(), getch() */
#include <stdio.h> /* putchar(), printf() */
#include <dos.h> /* inportb(), outportb(), getvect(), setvect(), delay() */

typedef struct
{
	queue_t rx, tx;
/* number of: interrupts, receive interrupts, transmit interrupts */
	unsigned int_count, rx_count, tx_count;
/* number of: framing errors, parity errors, overrun errors */
	unsigned ferr_count, perr_count, oerr_count;
	unsigned fifo_size;
/* hardware resources */
	unsigned io_adr, irq;
	vector_t vector;
} serial_t;
/*****************************************************************************
Identifies serial chip type (8250, 16550, etc.)
Returns FIFO size or 1 if no/defective FIFO. 16650+ detection is UNTESTED.
*****************************************************************************/
static unsigned serial_id(unsigned io_adr)
{
	unsigned i, j;

/* set EFR = 0 (16650+ chips only)
"The EFR can only be accessed after writing [0xBF] to the LCR..."
For 16550/A, this code zeroes the FCR instead */
	outportb(io_adr + 3, 0xBF);
	outportb(io_adr + 2, 0);
/* set FCR = 1 to enable FIFOs (if any) */
	outportb(io_adr + 3, 0);
	outportb(io_adr + 2, 0x01);
/* enabling FIFOs should set bits b7 and b6 in Interrupt ID register */
	i = inportb(io_adr + 2) & 0xC0;
	printf("Serial chip type: ");
/* no FIFO -- check if scratch register exists */
	if(i == 0)
	{
		outportb(io_adr + 7, 0xA5);
		outportb(0x80, 0xFF); /* prevent bus float returning 0xA5 */
		i = inportb(io_adr + 7);
		outportb(io_adr + 7, 0x5A);
		outportb(0x80, 0xFF); /* prevent bus float returning 0x5A */
		j = inportb(io_adr + 7);
/* scratch register 7 exists */
		if(i == 0xA5 && j == 0x5A)
			printf("8250A/16450 (no FIFO)\n");
		else
/* ALL 8250s (including 8250A) have serious problems... */
			printf("ewww, 8250/8250B (no FIFO)\n");
	}
	else if(i == 0x40)
		printf("UNKNOWN; assuming no FIFO\n");
	else if(i == 0x80)
		printf("16550; defective FIFO disabled\n");
	else if(i == 0xC0)
	{
/* for 16650+, should be able to read 0 from EFR
else will read 1 from FCR */
		outportb(io_adr + 3, 0xBF);
		if(inportb(io_adr + 2) == 0)
		{
			printf("16650+ (32-byte FIFO)\n");
			return 32;
		}
		else
		{
			printf("16550A (16-byte FIFO)\n");
			return 16;
		}
	}
	return 1;
}
/*****************************************************************************
Sets bit rate and number of data bits and optionally enables FIFO.

Also enables all interrupts except transmit
and clears dangling interrupts.
*****************************************************************************/
static int serial_init(serial_t *port, unsigned long baud,
		unsigned bits, char enable_fifo)
{
	unsigned divisor, io_adr, i;

	if(baud > 115200L || baud < 2)
	{
		printf("Bit rate (%lu) must be < 115200 and > 2\n", baud);
		return -1;
	}
	divisor = (unsigned)(115200L / baud);
	if(bits < 7 || bits > 8)
	{
		printf("Number of data bits (%u) must be 7 or 8\n", bits);
		return -1;
	}
/* set bit rate */
	io_adr = port->io_adr;
	outportb(io_adr + 3, 0x80);
	outportb(io_adr + 0, divisor);
	divisor >>= 8;
	outportb(io_adr + 1, divisor);
/* set number of data bits
This also sets no parity and 1 stop bit */
	outportb(io_adr + 3, (bits == 7) ? 2 : 3);
/* enable all interrupts EXCEPT transmit */
	outportb(io_adr + 1, 0x0D);
/* turn on FIFO, if any */
	if(port->fifo_size > 1 && enable_fifo)
		outportb(io_adr + 2, 0xC7);
	else
		outportb(io_adr + 2, 0);
/* loopback off, interrupt gate (Out2) on,
handshaking lines (RTS, DTR) off */
	outportb(io_adr + 4, 0x08);
/* clear dangling interrupts */
	while(1)
	{
		i = inportb(io_adr + 2);
		if(i & 0x01)
			break;
		(void)inportb(io_adr + 0);
		(void)inportb(io_adr + 5);
		(void)inportb(io_adr + 6);
	}
	return 0;
}
/*****************************************************************************
*****************************************************************************/
static serial_t *g_port;

static HANDLER(serial_irq)
{
/* count number of transmit and receive INTERRUPTS; not number of bytes */
	char rx_int = 0, tx_int = 0;
	unsigned reason;
	int stat, c, i;
	serial_t *port;

pokeb(0xB800, 0, peekb(0xB800, 0) + 1);
	port = g_port;
	port->int_count++;
	reason = inportb(port->io_adr + 2);
/* careful: a loop inside an interrupt handler can cause the system to hang */
	while((reason & 0x01) == 0)
	{
		reason >>= 1;
		reason &= 0x07;
/* line status interrupt (highest priority)
cleared by reading line status register (LSR; register 5) */
		if(reason == 3)
		{
			stat = inportb(port->io_adr + 5);
/* 0x80 == one or more errors in Rx FIFO
   0x10 == received BREAK */
			if(stat & 0x08) /* framing error */
				port->ferr_count++;
			if(stat & 0x04) /* parity error */
				port->perr_count++;
			if(stat & 0x02) /* overrun error */
				port->oerr_count++;
		}
/* receive data interrupt (2nd highest priority)
cleared by reading receiver buffer register (register 0) */
		else if(reason == 2)
		{
/* count ONE receive interrupt */
			if(rx_int == 0)
			{
				port->rx_count++;
				rx_int = 1;
			}
			(void)inq(&port->rx, inportb(port->io_adr + 0));
		}
/* character timeout interrupt (2nd highest priority; FIFO mode only)
cleared by receive buffer register */
		else if(reason == 6)
		{
/* count ONE receive interrupt */
			if(rx_int == 0)
			{
				port->rx_count++;
				rx_int = 1;
			}
			(void)inq(&port->rx, inportb(port->io_adr + 0));
		}
/* transmit holding register empty interrupt (3rd highest priority)
cleared by reading the interrupt ID register (IIR; register 2)
or by writing into transmit holding register (THR; register 0) */
		else if(reason == 1)
		{
/* queue up to port->fifo_size bytes */
			for(i = port->fifo_size; i != 0; i--)
			{
				c = deq(&port->tx);
				if(c < 0)
				{
/* empty transmit queue: disable further transmit interrupts */
					c = inportb(port->io_adr + 1);
					if(c & 0x02)
						outportb(port->io_adr + 1, c & ~0x02);
					break;
				}
/* count ONE transmit interrupt */
				if(tx_int == 0)
				{
					port->tx_count++;
					tx_int = 1;
				}
				outportb(port->io_adr + 0, c);
			}
		}
/* modem status interrupt (4th highest priority)
cleared by reading the modem status register (MSR; register 6) */
		else if(reason == 0)
		{
			(void)inportb(port->io_adr + 6);
		}
		reason = inportb(port->io_adr + 2);
	}
	outportb(0x20, 0x20);
}
/*****************************************************************************
Serial mouse state machine. Format of serial mouse packet:

byte 0:	b6	=1
	b5	=left button state (1=button pressed)
	b4	=right button state (1=button pressed)
	b3:b2	=b7:b6 of Y delta
	b1:b0	=b7:b6 of X delta

byte 1:	b6	=0
	b5:b0	=b5:b0 of X delta

byte 2:	b6	=0
	b5:b0	=b5:b0 of Y delta

byte 3: (3-button mouse only. This byte is sent only when the center
	button is pressed, released, or held down while some other
	mouse event occurs.)
	b6	=0
	b5	=center button state (1=button pressed)
	b4:b0	=0
*****************************************************************************/
static void serial_mouse(unsigned char byte)
{
	static unsigned screen_wd, screen_ht; /* text-mode screen size */
	static unsigned char state, buf[4];
	static short old_x, old_y;
/**/
	short new_x, new_y, delta_x, delta_y;
	unsigned offset;

/* get text screen size from BIOS */
	if(screen_wd == 0)
	{
		screen_wd = peekw(0x40, 0x4A);
		screen_ht = peekb(0x40, 0x84) + 1;
	}
/* byte with b6 set is first byte in new mouse packet */
	if(byte & 0x40)
		state = 0;
/* mouse packets larger than 4 bytes are invalid */
	if(state >= 4)
		return;
/* store byte and advance to next state */
	buf[state] = byte & 0x3F;
	state++;
/* completed packet? */
	if(state == 3)
	{
/* compute new mouse cursor position */
		delta_x = buf[0] & 0x03;
		delta_x <<= 6;
		delta_x |= (buf[1] & 0x3F);
/* sign-extend bytes to signed int/short */
		delta_x = (signed char)delta_x;

		delta_y = buf[0] & 0x0C;
		delta_y <<= 4;
		delta_y |= (buf[2] & 0x3F);
		delta_y = (signed char)delta_y;

		new_x = old_x + delta_x;
		new_y = old_y + delta_y;
/* clip mouse cursor position against screen size */
		if(new_x < 0)
			new_x = 0;
		if((unsigned)new_x > screen_wd - 1)
			new_x = screen_wd - 1;
		if(new_y < 0)
			new_y = 0;
		if((unsigned)new_y > screen_ht - 1)
			new_y = screen_ht - 1;
/* erase old cursor */
		offset = (old_y * screen_wd + old_x) * 2 + 1;
		pokeb(0xB800, offset, peekb(0xB800, offset) ^ 0x60);
/* draw new cursor */
		offset = (new_y * screen_wd + new_x) * 2 + 1;
		pokeb(0xB800, offset, peekb(0xB800, offset) ^ 0x60);
/* update old pos'n */
		old_x = new_x;
		old_y = new_y;
/* draw button state */
		if(buf[0] & 0x20) /* left button */
			pokeb(0xB800, 0x80, 'L');
		else
			pokeb(0xB800, 0x80, ' ');
		pokeb(0xB800, 0x81, 0x60);
		if(buf[0] & 0x10) /* right button */
			pokeb(0xB800, 0x88, 'R');
		else
			pokeb(0xB800, 0x88, ' ');
		pokeb(0xB800, 0x89, 0x60);
	}
/* 4-byte packet indicates 3-button mouse */
	else if(state == 4)
	{
		if(buf[3] & 0x20) /* center button */
			pokeb(0xB800, 0x84, 'C');
		else
			pokeb(0xB800, 0x84, ' ');
		pokeb(0xB800, 0x85, 0x60);
	}
}
/*****************************************************************************
*****************************************************************************/
#define BPERL		16	/* byte/line for dump */

static void dump(void *data_p, unsigned count)
{
	unsigned char *data = (unsigned char *)data_p;
	unsigned byte1, byte2;

	while(count != 0)
	{
		for(byte1 = 0; byte1 < BPERL; byte1++)
		{
			if(count == 0)
				break;
			printf("%02X ", data[byte1]);
			count--;
		}
		printf("\t");
		for(byte2 = 0; byte2 < byte1; byte2++)
		{
			if(data[byte2] < ' ')
				printf(".");
			else
				printf("%c", data[byte2]);
		}
		printf("\n");
		data += BPERL;
	}
}
/*****************************************************************************
s[n]	name		size	req'd	descr
----	-----------	----	-----	---------
0	Begin PnP	1	yes	begin PnP ID == "(", either 0x28 or 0x08

1-2	PnP Rev		2	yes	Plug and Play revision (e.g. 0x00,01)
3-5	EISA ID		3	yes	EISA determined unique Mfr Identifier
6-9	Product ID	4	yes	Mfr determined unique Product Identifier

10	Extend		1	no	"\": either 0x5C or 0x3C.  see note below.
11-18	Serial Number	8	no	optional device serial number

19	Extend		1	no
20-	Class Name	<33	no	Plug and Play Class Identifier (Annex C)

?	Extend		1	no
?	Driver ID	<41	no	Compatible Device IDs

?	Extend		1	no
?	User Name	<41	no	human-readable Product Description

?	Checksum	2	[*]	8 bit arithmetic checksum of all characters
					from Begin PnP to End PnP inclusive,
					exclusive of the checksum bytes themselves,
					represented as a two character hexadecimal
					number
?	End PnP		1	yes	End PnP ID == ")", either 0x29 or 0x09
*****************************************************************************/
static int display_pnp_id(unsigned char *s, unsigned len)
{
/* s[0] = Begin byte = 0x28 or 0x08 */
	for(; *s != '(' && *s != 0x08; s++)
	{
		if(len == 0)
BAD:		{
			printf("Bad PnP serial ID\n");
			return -1;
		}
		len--;
	}
	if(len < 11)
		goto BAD;
/* s[10] = End byte = 0x29 or 0x09, or Extend byte = 0x5C or 0x3C */
	if(s[10] != ')' && s[10] != 0x09 && s[10] != '\\' && s[10] != 0x3C)
		goto BAD;
	printf("Serial PnP device: PnP rev %u.%u, ID '%c%c%c%c%c%c%c'\n",
/* s[1] ... s[2] = PnP revision */
		s[1], s[2],
/* s[3] ... s[5] = EISA Vendor ID (e.g. 'LGI' for Logitech) */
		'@' + (s[3] & 0x1F),
		'@' + (s[4] & 0x1F),
		'@' + (s[5] & 0x1F),
/* s[6] ... [s9] = Product ID number */
		'0' + (s[6] & 0x0F),
		'0' + (s[7] & 0x0F),
		'0' + (s[8] & 0x0F),
		'0' + (s[9] & 0x0F));
	return 0;
}
/*****************************************************************************
do this with ANSI? need these escapes:
- Esc[s		save current cursor position
- Esc[7		save current cursor pos'n and attributes
- Esc[u		restore old cursor position
- Esc[8		restore old cursor pos'n and attributes
- Esc[???	set window size and home cursor in new window
*****************************************************************************/
static void display_modem_status(unsigned io_adr)
{
	static const char *signal_name[] =
	{
		"DCD", "RI", "DSR", "CTS", /* incoming */
		"RTS", "DTR" /* outgoing */
	};
/**/
	unsigned status, csr_pos, i, mask, attrib;
	const char *s;

	i = 0;
	csr_pos = 40;
/* incoming */
	status = inp(io_adr + 6);
	for(mask = 0x80; mask > 0x08; mask >>= 1)
	{
		if(status & mask)
			attrib = 0x1F00; /* bright white (F) on blue (1) */
		else
			attrib = 0x7800; /* gray (8) on white (7) */
		for(s = signal_name[i]; *s != '\0'; s++)
		{
			pokew(0xB800, csr_pos, attrib | *s);
			csr_pos += 2;
		}
		csr_pos += 2;
		i++;
	}
	csr_pos += 8;
/* outgoing */
	status = inp(io_adr + 4);
	for(mask = 0x02; mask != 0; mask >>= 1)
	{
		if(status & mask)
			attrib = 0x1F00; /* bright white (F) on blue (1) */
		else
			attrib = 0x7800; /* gray (8) on white (7) */
		for(s = signal_name[i]; *s != '\0'; s++)
		{
			pokew(0xB800, csr_pos, attrib | *s);
			csr_pos += 2;
		}
		csr_pos += 2;
		i++;
	}
}
/*****************************************************************************
*****************************************************************************/
#define	MAX_ID		32
#define	BUF_SIZE	256

int main(int arg_c, char *arg_v[])
{
/* xxx - allocate these from the heap? */
	static unsigned char rx_buf[BUF_SIZE], tx_buf[BUF_SIZE];
/**/
	unsigned char buf[MAX_ID];
	int num_ports, i, j, k;
	serial_t port;

/* there are up to 4 COM port I/O addresses stored
in the BIOS data area, at address 400h */
	num_ports = 0;
	for(i = 0; i < 4; i++)
	{
		if(peekw(0x40, 0 + 2 * i) == 0)
			break;
		num_ports++;
	}
/* check args */
	if(arg_c != 2)
	{
		printf("Serial mouse driver/demo. Specify a COM port 1-%u\n",
			num_ports);
		return 1;
	}
	sscanf(arg_v[1], "%u", &i);
	if(i < 1 || i > num_ports)
		goto NONE;
/* init serial_t struct */
	memset(&port, 0, sizeof(port));
	port.rx.buf = rx_buf;
	port.rx.size = BUF_SIZE;
	port.tx.buf = tx_buf;
	port.tx.size = BUF_SIZE;
	port.io_adr = peekw(0x40, 0 + 2 * (i - 1));
	if(port.io_adr == 0)
NONE:	{
		printf("Sorry, no COM %u on this PC\n", i);
		return 1;
	}
	if(i & 0x01)
		port.irq = 4;	/* COM1 or COM3 */
	else
		port.irq = 3;	/* COM2 or COM4 */
/* detect serial chip type and FIFO size */
	port.fifo_size = serial_id(port.io_adr);
	printf("I/O=0x%03X, IRQ=%u, BUF_SIZE=%u\n", port.io_adr,
		port.irq, BUF_SIZE);
	g_port = &port;
#if defined(MOUSE)
/* set 1200 baud, 7N1, FIFO off */
	if(serial_init(&port, 1200, 7, 0) != 0)
#else
/* set 115200 baud, 8N1, FIFO on */
	if(serial_init(&port, 115200L, 8, 1) != 0)
#endif
	{
		printf("Error setting serial port bit rate\n");
		return 2;
	}
#if defined(MOUSE)
/* serial_init() turned off handshaking lines, cutting power to the mouse.
Wait 100 ms, then turn power back on */
	delay(100);
	outportb(port.io_adr + 4, 0x0B);
#endif
/* hook serial hardware interrupt */
	if(hook_irq(&port.vector, port.irq, serial_irq) != 0)
	{
		printf("Error hooking IRQ %u\n", port.irq);
		return 2;
	}
#if defined(MOUSE)
/* collect and analyze mouse ID */
	i = 0;
	for(j = 100; j != 0; j--)
	{
		k = deq(&port.rx);
		if(k < 0)
		{
			delay(10);
			continue;
		}
		j = 100; /* reset timeout */
		buf[i] = k;
		i++;
		if(i >= MAX_ID)
			break;
	}
	if(i == 0)
	{
		printf("No serial mouse detected\n");
		goto END;
	}
/* 'M' */
	/*else*/ if(i == 1 && buf[0] == 'M')
		printf("2-button mouse detected\n");
/* 'M3' */
	else if(i == 2 && buf[0] == 'M' && buf[1] == '3')
		printf("3-button mouse detected\n");
/* PnP mouse */
	else if(i >= 13)
	{
		if(display_pnp_id(buf, i) != 0)
			goto END;
	}
/* WTF? */
	else
	{
		printf("Unknown mouse type; assuming 2-button mouse. "
			"Dump of ID bytes:\n");
		dump(buf, i);
	}
#endif
/* main loop */
	printf("Press Esc to quit\n");
	while(1)
	{
#if !defined(MOUSE)
		display_modem_status(port.io_adr);
#endif
		i = deq(&port.rx);
		if(i >= 0)
#if defined(MOUSE)
			serial_mouse(i);
#else
			putch(i);
#endif
		if(!kbhit())
			continue;
		i = getch();
		if(i == 0)
			i = 0x100 | getch();
		if(i == 27)
			break;
/* F1 toggles RTS */
		if(i == 0x13B)
		{
			outportb(port.io_adr + 4,
				inportb(port.io_adr + 4) ^ 0x02);
			continue;
		}
/* F2 toggles DTR */
		if(i == 0x13C)
		{
			outportb(port.io_adr + 4,
				inportb(port.io_adr + 4) ^ 0x01);
			continue;
		}
		inq(&port.tx, i);
/* kick-start transmit by (re)enabling transmit interrupt */
		outportb(port.io_adr + 1, inportb(port.io_adr + 1) | 0x02);
	}
END:
/* remove interrupt handler */
	unhook(&port.vector);
/* display statistics */
	printf("Total interrupts   : %5u\t", port.int_count);
	printf("Receive interrupts : %5u\n", port.rx_count);
	printf("Transmit interrupts: %5u\t", port.tx_count);
	printf("Framing errors     : %5u\n", port.ferr_count);
	printf("Parity errors      : %5u\t", port.perr_count);
	printf("Overrun errors     : %5u\n", port.oerr_count);
	printf("\n\n");
	return 0;
}

